package tp.pr5.mv;

import java.util.Random;

public class Memory extends Observable<Memory.Observer> {
	
//	 La clase memoria consta de un array de MemoryTAD (dos enteros: posición y valor), el contador del array y 
//	 y un tamaño que podrá aumentarse en caso de que se llene. 

	private MemoryCell[] memory;
	private int mCounter;
	private static int MEMORY_SIZE = 200;   
	
	
	public Memory() {  // Constructor base.
		memory = new MemoryCell[MEMORY_SIZE];
		mCounter = 0;
	}
	
	 public static interface Observer  {
	  	
		public void onReset();
		public void onWrite(int pos, int val);
	
	  }
	 
	public void reset() {
		memory = new MemoryCell[MEMORY_SIZE];
		mCounter = 0;
		for ( Observer o: observers ) 
			o.onReset();
	}
	
	public int getMemValue( int pos ) {
		return memory[pos].getValue();
	}
	
	public int getMemPosition( int pos ) {
		return memory[pos].getPosition();
	}
	  
   public int getCounter() {  // Get del contador del array.
		return mCounter;
	}
	
	
	private boolean isFull() {                  // Nos dice si la memoria est� llena.
		
		return (mCounter == MEMORY_SIZE);
		
	}
	
   public boolean isEmpty() {                 // Nos dice si la memoria est� vac�a.
		
		return (mCounter == 0);
		
	}
   
	private void extend() {                    // Método que extiende la memoria y aumenta en 50 el espacio.
		
		
		MemoryCell[] auxVector = new MemoryCell[MEMORY_SIZE + 50];  // Creamos un vector auxiliar con 50 u. más de espacio.
		
		for ( int i = 0; i < memory.length; i++) {                // Metemos en ese vector los valores de nuestro array.
			
			auxVector[i].setPosition(memory[i].getPosition());
			auxVector[i].setValue(memory[i].getValue());
			
		}
		
		memory = auxVector;                    // Nuestro vector pasa a referenciar al auxiliar.
		
		MEMORY_SIZE = MEMORY_SIZE + 50;
		
	}
	
	
	public void insert(MemoryCell value, int posInsert ) {    // Método de insertar en array ordenado.
		

		for ( int j = this.mCounter; j > posInsert; j--  ) {      // Hacemos hueco.
			memory[j] = memory[j-1];
		}
			
		memory[posInsert] = value;                // Insertamos.
		
		mCounter++;                           // Aumentamos el contador.
		
	}
	
	
	public int binarySearch(int position) {   // M�todo que implementa la b�squeda binaria.
		
		int pos = -1;
		int a = 0, b = mCounter-1, m = 0;
		boolean found = false;
		
		while ( a <= b && !found )
		{
			m = (a+b) / 2;
			
			if ( memory[m].getPosition() == position )
				found = true;
				
			else if ( memory[m].getPosition() > position) 
				b = m - 1;

			else 
				a = m + 1;

		}
		

		if ( !found  &&  mCounter > 0 )    // Si no se ha encontrado ajustamos el �ndice.
		{	
			if ( memory[m].getPosition() < position )
				pos = -1*(m + 1) - 1;                 // Si no est� devolvemos negativo ( -1 0, -2 1, -3 2, ... ) pos real ser�a m+1
			else 
				pos = -1*m - 1;
        }		    		
		else if ( mCounter > 0 )
			pos = m;
			
		return pos;                          // Devuelve la posici�n, y si no ha encontrado el elemento devuelve d�nde deber�a estar.
		
		
	}
	

	public boolean canRead(int pos){        // Se podr� leer si la posici�n es mayor que 0 (suponemos memoria infinita).
		
		return (pos >= 0);
		
	}
	
	
	public int read (int pos) {             // M�todo de lectura.
		
		int readValue = 0;
		
		if ( canRead(pos)) {                                // Si podemos leer ...
			
			int searched = binarySearch(pos);               // Cogemos su posici�n en el array de acuerdo a la "posici�n" de memoria.
			
			if ( searched >= 0 ) {
				readValue = memory[searched].getValue() ;   // Si lo ha encontrado, devolveremos el valor con esa posici�n.
			}
			else {
				Random ran = new Random();                  // Si no lo ha encontrado, devolveremos un valor aleatorio.
				readValue = ran.nextInt(10000);      
			}
		}
		else 
			
			System.out.println(" Error! The memory position is a negative number");      // Error de posici�n negativa.
		
		return readValue;
		
	}
	
	
	public boolean write (int pos, int value) {               // M�todo de escritura.
		
		boolean canWrite = false;                             // Devolveremos un booleano que indica el �xito o fracaso.
		
		if ( canRead(pos) ) {                                 // Si podemos leer...
			
			canWrite = true;
			
			int searched = binarySearch(pos);                // Buscamos su posici�n.
			
			if ( searched >= 0 )                             // Si lo hemos encontrado cambiamos su valor en memoria.
				
				memory[searched].setValue(value);          
			
			else {                                           // Si no lo hemos encontrado, crearemos un objeto memoryTAD 
				                                             // con esa posici�n y ese valor y lo a�adimos a memoria.			
				
				if( isFull() )                               // Si la memoria est� llena, extendemos el array.                        
					extend();
				
				MemoryCell cell = new MemoryCell(pos,value);
				
				int posInsert = -1*(searched + 1);          // Deshacemos y vemos la pos. donde debería estar.
				insert(cell, posInsert);                    // Lo insertamos.
				
			}
			
			for ( Observer o: observers ) {
				o.onWrite(pos, value);
			}
		}
		
		return canWrite;
		
	}
	
	
}

